{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 宽度深度模型模板"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![](https://img-blog.csdn.net/20170502135611349)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "讲一个用tensorflow高级接口搭建wide and deep model的案例，然后切到ctr预估问题中"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "reference：[google官方案例](https://github.com/tensorflow/models/tree/master/official/wide_deep)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "--2018-05-05 09:41:10--  https://raw.githubusercontent.com/tensorflow/models/master/official/wide_deep/data_download.py\n",
      "Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 151.101.228.133\n",
      "Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|151.101.228.133|:443... connected.\n",
      "HTTP request sent, awaiting response... 200 OK\n",
      "Length: 2357 (2.3K) [text/plain]\n",
      "Saving to: ‘data_download.py’\n",
      "\n",
      "100%[======================================>] 2,357       --.-K/s   in 0s      \n",
      "\n",
      "2018-05-05 09:41:11 (48.5 MB/s) - ‘data_download.py’ saved [2357/2357]\n",
      "\n"
     ]
    }
   ],
   "source": [
    "!wget https://raw.githubusercontent.com/tensorflow/models/master/official/wide_deep/data_download.py"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# %load data_download.py\n",
    "# Copyright 2017 The TensorFlow Authors. All Rights Reserved.\n",
    "#\n",
    "# Licensed under the Apache License, Version 2.0 (the \"License\");\n",
    "# you may not use this file except in compliance with the License.\n",
    "# You may obtain a copy of the License at\n",
    "#\n",
    "#     http://www.apache.org/licenses/LICENSE-2.0\n",
    "#\n",
    "# Unless required by applicable law or agreed to in writing, software\n",
    "# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
    "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
    "# See the License for the specific language governing permissions and\n",
    "# limitations under the License.\n",
    "# ==============================================================================\n",
    "\"\"\"Download and clean the Census Income Dataset.\"\"\"\n",
    "\n",
    "from __future__ import absolute_import\n",
    "from __future__ import division\n",
    "from __future__ import print_function\n",
    "\n",
    "import argparse\n",
    "import os\n",
    "import sys\n",
    "\n",
    "from six.moves import urllib\n",
    "import tensorflow as tf\n",
    "\n",
    "DATA_URL = 'https://archive.ics.uci.edu/ml/machine-learning-databases/adult'\n",
    "TRAINING_FILE = 'adult.data'\n",
    "TRAINING_URL = '%s/%s' % (DATA_URL, TRAINING_FILE)\n",
    "EVAL_FILE = 'adult.test'\n",
    "EVAL_URL = '%s/%s' % (DATA_URL, EVAL_FILE)\n",
    "\n",
    "parser = argparse.ArgumentParser()\n",
    "\n",
    "parser.add_argument(\n",
    "    '--data_dir', type=str, default='/tmp/census_data',\n",
    "    help='Directory to download census data')\n",
    "\n",
    "\n",
    "def _download_and_clean_file(filename, url):\n",
    "  \"\"\"Downloads data from url, and makes changes to match the CSV format.\"\"\"\n",
    "  temp_file, _ = urllib.request.urlretrieve(url)\n",
    "  with tf.gfile.Open(temp_file, 'r') as temp_eval_file:\n",
    "    with tf.gfile.Open(filename, 'w') as eval_file:\n",
    "      for line in temp_eval_file:\n",
    "        line = line.strip()\n",
    "        line = line.replace(', ', ',')\n",
    "        if not line or ',' not in line:\n",
    "          continue\n",
    "        if line[-1] == '.':\n",
    "          line = line[:-1]\n",
    "        line += '\\n'\n",
    "        eval_file.write(line)\n",
    "  tf.gfile.Remove(temp_file)\n",
    "\n",
    "\n",
    "def main(_):\n",
    "  if not tf.gfile.Exists(FLAGS.data_dir):\n",
    "    tf.gfile.MkDir(FLAGS.data_dir)\n",
    "\n",
    "  training_file_path = os.path.join(FLAGS.data_dir, TRAINING_FILE)\n",
    "  _download_and_clean_file(training_file_path, TRAINING_URL)\n",
    "\n",
    "  eval_file_path = os.path.join(FLAGS.data_dir, EVAL_FILE)\n",
    "  _download_and_clean_file(eval_file_path, EVAL_URL)\n",
    "\n",
    "\n",
    "if __name__ == '__main__':\n",
    "  FLAGS, unparsed = parser.parse_known_args()\n",
    "  tf.app.run(argv=[sys.argv[0]] + unparsed)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "!python data_download.py"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "--2018-05-05 09:41:52--  https://raw.githubusercontent.com/tensorflow/models/master/official/wide_deep/wide_deep.py\n",
      "Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 151.101.228.133\n",
      "Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|151.101.228.133|:443... connected.\n",
      "HTTP request sent, awaiting response... 200 OK\n",
      "Length: 9540 (9.3K) [text/plain]\n",
      "Saving to: ‘wide_deep.py’\n",
      "\n",
      "100%[======================================>] 9,540       --.-K/s   in 0s      \n",
      "\n",
      "2018-05-05 09:41:53 (91.5 MB/s) - ‘wide_deep.py’ saved [9540/9540]\n",
      "\n"
     ]
    }
   ],
   "source": [
    "!wget https://raw.githubusercontent.com/tensorflow/models/master/official/wide_deep/wide_deep.py"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# %load wide_deep.py\n",
    "# Copyright 2017 The TensorFlow Authors. All Rights Reserved.\n",
    "#\n",
    "# Licensed under the Apache License, Version 2.0 (the \"License\");\n",
    "# you may not use this file except in compliance with the License.\n",
    "# You may obtain a copy of the License at\n",
    "#\n",
    "#     http://www.apache.org/licenses/LICENSE-2.0\n",
    "#\n",
    "# Unless required by applicable law or agreed to in writing, software\n",
    "# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
    "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
    "# See the License for the specific language governing permissions and\n",
    "# limitations under the License.\n",
    "# ==============================================================================\n",
    "\"\"\"Example code for TensorFlow Wide & Deep Tutorial using tf.estimator API.\"\"\"\n",
    "from __future__ import absolute_import\n",
    "from __future__ import division\n",
    "from __future__ import print_function\n",
    "\n",
    "import os\n",
    "import shutil\n",
    "\n",
    "from absl import app as absl_app\n",
    "from absl import flags\n",
    "import tensorflow as tf  # pylint: disable=g-bad-import-order\n",
    "\n",
    "from official.utils.flags import core as flags_core\n",
    "from official.utils.logs import hooks_helper\n",
    "from official.utils.misc import model_helpers\n",
    "\n",
    "\n",
    "_CSV_COLUMNS = [\n",
    "    'age', 'workclass', 'fnlwgt', 'education', 'education_num',\n",
    "    'marital_status', 'occupation', 'relationship', 'race', 'gender',\n",
    "    'capital_gain', 'capital_loss', 'hours_per_week', 'native_country',\n",
    "    'income_bracket'\n",
    "]\n",
    "\n",
    "_CSV_COLUMN_DEFAULTS = [[0], [''], [0], [''], [0], [''], [''], [''], [''], [''],\n",
    "                        [0], [0], [0], [''], ['']]\n",
    "\n",
    "_NUM_EXAMPLES = {\n",
    "    'train': 32561,\n",
    "    'validation': 16281,\n",
    "}\n",
    "\n",
    "\n",
    "LOSS_PREFIX = {'wide': 'linear/', 'deep': 'dnn/'}\n",
    "\n",
    "\n",
    "def define_wide_deep_flags():\n",
    "  \"\"\"Add supervised learning flags, as well as wide-deep model type.\"\"\"\n",
    "  flags_core.define_base()\n",
    "\n",
    "  flags.adopt_module_key_flags(flags_core)\n",
    "\n",
    "  flags.DEFINE_enum(\n",
    "      name=\"model_type\", short_name=\"mt\", default=\"wide_deep\",\n",
    "      enum_values=['wide', 'deep', 'wide_deep'],\n",
    "      help=\"Select model topology.\")\n",
    "\n",
    "  flags_core.set_defaults(data_dir='/tmp/census_data',\n",
    "                          model_dir='/tmp/census_model',\n",
    "                          train_epochs=40,\n",
    "                          epochs_between_evals=2,\n",
    "                          batch_size=40)\n",
    "\n",
    "\n",
    "def build_model_columns():\n",
    "  \"\"\"Builds a set of wide and deep feature columns.\"\"\"\n",
    "  # Continuous columns\n",
    "  age = tf.feature_column.numeric_column('age')\n",
    "  education_num = tf.feature_column.numeric_column('education_num')\n",
    "  capital_gain = tf.feature_column.numeric_column('capital_gain')\n",
    "  capital_loss = tf.feature_column.numeric_column('capital_loss')\n",
    "  hours_per_week = tf.feature_column.numeric_column('hours_per_week')\n",
    "\n",
    "  education = tf.feature_column.categorical_column_with_vocabulary_list(\n",
    "      'education', [\n",
    "          'Bachelors', 'HS-grad', '11th', 'Masters', '9th', 'Some-college',\n",
    "          'Assoc-acdm', 'Assoc-voc', '7th-8th', 'Doctorate', 'Prof-school',\n",
    "          '5th-6th', '10th', '1st-4th', 'Preschool', '12th'])\n",
    "\n",
    "  marital_status = tf.feature_column.categorical_column_with_vocabulary_list(\n",
    "      'marital_status', [\n",
    "          'Married-civ-spouse', 'Divorced', 'Married-spouse-absent',\n",
    "          'Never-married', 'Separated', 'Married-AF-spouse', 'Widowed'])\n",
    "\n",
    "  relationship = tf.feature_column.categorical_column_with_vocabulary_list(\n",
    "      'relationship', [\n",
    "          'Husband', 'Not-in-family', 'Wife', 'Own-child', 'Unmarried',\n",
    "          'Other-relative'])\n",
    "\n",
    "  workclass = tf.feature_column.categorical_column_with_vocabulary_list(\n",
    "      'workclass', [\n",
    "          'Self-emp-not-inc', 'Private', 'State-gov', 'Federal-gov',\n",
    "          'Local-gov', '?', 'Self-emp-inc', 'Without-pay', 'Never-worked'])\n",
    "\n",
    "  # To show an example of hashing:\n",
    "  occupation = tf.feature_column.categorical_column_with_hash_bucket(\n",
    "      'occupation', hash_bucket_size=1000)\n",
    "\n",
    "  # Transformations.\n",
    "  age_buckets = tf.feature_column.bucketized_column(\n",
    "      age, boundaries=[18, 25, 30, 35, 40, 45, 50, 55, 60, 65])\n",
    "\n",
    "  # Wide columns and deep columns.\n",
    "  base_columns = [\n",
    "      education, marital_status, relationship, workclass, occupation,\n",
    "      age_buckets,\n",
    "  ]\n",
    "\n",
    "  crossed_columns = [\n",
    "      tf.feature_column.crossed_column(\n",
    "          ['education', 'occupation'], hash_bucket_size=1000),\n",
    "      tf.feature_column.crossed_column(\n",
    "          [age_buckets, 'education', 'occupation'], hash_bucket_size=1000),\n",
    "  ]\n",
    "\n",
    "  wide_columns = base_columns + crossed_columns\n",
    "\n",
    "  deep_columns = [\n",
    "      age,\n",
    "      education_num,\n",
    "      capital_gain,\n",
    "      capital_loss,\n",
    "      hours_per_week,\n",
    "      tf.feature_column.indicator_column(workclass),\n",
    "      tf.feature_column.indicator_column(education),\n",
    "      tf.feature_column.indicator_column(marital_status),\n",
    "      tf.feature_column.indicator_column(relationship),\n",
    "      # To show an example of embedding\n",
    "      tf.feature_column.embedding_column(occupation, dimension=8),\n",
    "  ]\n",
    "\n",
    "  return wide_columns, deep_columns\n",
    "\n",
    "\n",
    "def build_estimator(model_dir, model_type):\n",
    "  \"\"\"Build an estimator appropriate for the given model type.\"\"\"\n",
    "  wide_columns, deep_columns = build_model_columns()\n",
    "  hidden_units = [100, 75, 50, 25]\n",
    "\n",
    "  # Create a tf.estimator.RunConfig to ensure the model is run on CPU, which\n",
    "  # trains faster than GPU for this model.\n",
    "  run_config = tf.estimator.RunConfig().replace(\n",
    "      session_config=tf.ConfigProto(device_count={'GPU': 0}))\n",
    "\n",
    "  if model_type == 'wide':\n",
    "    return tf.estimator.LinearClassifier(\n",
    "        model_dir=model_dir,\n",
    "        feature_columns=wide_columns,\n",
    "        config=run_config)\n",
    "  elif model_type == 'deep':\n",
    "    return tf.estimator.DNNClassifier(\n",
    "        model_dir=model_dir,\n",
    "        feature_columns=deep_columns,\n",
    "        hidden_units=hidden_units,\n",
    "        config=run_config)\n",
    "  else:\n",
    "    return tf.estimator.DNNLinearCombinedClassifier(\n",
    "        model_dir=model_dir,\n",
    "        linear_feature_columns=wide_columns,\n",
    "        dnn_feature_columns=deep_columns,\n",
    "        dnn_hidden_units=hidden_units,\n",
    "        config=run_config)\n",
    "\n",
    "\n",
    "def input_fn(data_file, num_epochs, shuffle, batch_size):\n",
    "  \"\"\"Generate an input function for the Estimator.\"\"\"\n",
    "  assert tf.gfile.Exists(data_file), (\n",
    "      '%s not found. Please make sure you have run data_download.py and '\n",
    "      'set the --data_dir argument to the correct path.' % data_file)\n",
    "\n",
    "  def parse_csv(value):\n",
    "    print('Parsing', data_file)\n",
    "    columns = tf.decode_csv(value, record_defaults=_CSV_COLUMN_DEFAULTS)\n",
    "    features = dict(zip(_CSV_COLUMNS, columns))\n",
    "    labels = features.pop('income_bracket')\n",
    "    return features, tf.equal(labels, '>50K')\n",
    "\n",
    "  # Extract lines from input files using the Dataset API.\n",
    "  dataset = tf.data.TextLineDataset(data_file)\n",
    "\n",
    "  if shuffle:\n",
    "    dataset = dataset.shuffle(buffer_size=_NUM_EXAMPLES['train'])\n",
    "\n",
    "  dataset = dataset.map(parse_csv, num_parallel_calls=5)\n",
    "\n",
    "  # We call repeat after shuffling, rather than before, to prevent separate\n",
    "  # epochs from blending together.\n",
    "  dataset = dataset.repeat(num_epochs)\n",
    "  dataset = dataset.batch(batch_size)\n",
    "  return dataset\n",
    "\n",
    "\n",
    "def export_model(model, model_type, export_dir):\n",
    "  \"\"\"Export to SavedModel format.\n",
    "\n",
    "  Args:\n",
    "    model: Estimator object\n",
    "    model_type: string indicating model type. \"wide\", \"deep\" or \"wide_deep\"\n",
    "    export_dir: directory to export the model.\n",
    "  \"\"\"\n",
    "  wide_columns, deep_columns = build_model_columns()\n",
    "  if model_type == 'wide':\n",
    "    columns = wide_columns\n",
    "  elif model_type == 'deep':\n",
    "    columns = deep_columns\n",
    "  else:\n",
    "    columns = wide_columns + deep_columns\n",
    "  feature_spec = tf.feature_column.make_parse_example_spec(columns)\n",
    "  example_input_fn = (\n",
    "      tf.estimator.export.build_parsing_serving_input_receiver_fn(feature_spec))\n",
    "  model.export_savedmodel(export_dir, example_input_fn)\n",
    "\n",
    "\n",
    "def run_wide_deep(flags_obj):\n",
    "  \"\"\"Run Wide-Deep training and eval loop.\n",
    "\n",
    "  Args:\n",
    "    flags_obj: An object containing parsed flag values.\n",
    "  \"\"\"\n",
    "\n",
    "  # Clean up the model directory if present\n",
    "  shutil.rmtree(flags_obj.model_dir, ignore_errors=True)\n",
    "  model = build_estimator(flags_obj.model_dir, flags_obj.model_type)\n",
    "\n",
    "  train_file = os.path.join(flags_obj.data_dir, 'adult.data')\n",
    "  test_file = os.path.join(flags_obj.data_dir, 'adult.test')\n",
    "\n",
    "  # Train and evaluate the model every `flags.epochs_between_evals` epochs.\n",
    "  def train_input_fn():\n",
    "    return input_fn(\n",
    "        train_file, flags_obj.epochs_between_evals, True, flags_obj.batch_size)\n",
    "\n",
    "  def eval_input_fn():\n",
    "    return input_fn(test_file, 1, False, flags_obj.batch_size)\n",
    "\n",
    "  loss_prefix = LOSS_PREFIX.get(flags_obj.model_type, '')\n",
    "  train_hooks = hooks_helper.get_train_hooks(\n",
    "      flags_obj.hooks, batch_size=flags_obj.batch_size,\n",
    "      tensors_to_log={'average_loss': loss_prefix + 'head/truediv',\n",
    "                      'loss': loss_prefix + 'head/weighted_loss/Sum'})\n",
    "\n",
    "  # Train and evaluate the model every `flags.epochs_between_evals` epochs.\n",
    "  for n in range(flags_obj.train_epochs // flags_obj.epochs_between_evals):\n",
    "    model.train(input_fn=train_input_fn, hooks=train_hooks)\n",
    "    results = model.evaluate(input_fn=eval_input_fn)\n",
    "\n",
    "    # Display evaluation metrics\n",
    "    print('Results at epoch', (n + 1) * flags_obj.epochs_between_evals)\n",
    "    print('-' * 60)\n",
    "\n",
    "    for key in sorted(results):\n",
    "      print('%s: %s' % (key, results[key]))\n",
    "\n",
    "    if model_helpers.past_stop_threshold(\n",
    "        flags_obj.stop_threshold, results['accuracy']):\n",
    "      break\n",
    "\n",
    "  # Export the model\n",
    "  if flags_obj.export_dir is not None:\n",
    "    export_model(model, flags_obj.model_type, flags_obj.export_dir)\n",
    "\n",
    "\n",
    "def main(_):\n",
    "  run_wide_deep(flags.FLAGS)\n",
    "\n",
    "\n",
    "if __name__ == '__main__':\n",
    "  tf.logging.set_verbosity(tf.logging.INFO)\n",
    "  define_wide_deep_flags()\n",
    "  absl_app.run(main)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "!python wide_deep.py"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 2",
   "language": "python",
   "name": "python2"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 2
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython2",
   "version": "2.7.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
